#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
LimeReader macOS .app 打包工具
将编译后的程序打包成标准的macOS应用程序包

功能:
1. 创建标准的.app包结构
2. 复制可执行文件和依赖库
3. 生成Info.plist配置文件
4. 集成Qt框架和插件
5. 设置应用图标和资源
6. 配置代码签名（可选）

作者: Wang Zhen
版本: 1.0.0
"""

import os
import sys
import json
import shutil
import subprocess
from pathlib import Path
from typing import Dict, Any, List, Optional
from datetime import datetime
import logging

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('app_creation.log', encoding='utf-8'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

class MacOSAppCreator:
    """macOS .app 创建器"""

    def __init__(self, config_file: str = 'release_config.json'):
        """
        初始化App创建器

        Args:
            config_file: 配置文件路径
        """
        self.config_file = config_file
        self.config = self.load_config()
        self.qt_path = None
        self.deployment_tool = None

    def load_config(self) -> Dict[str, Any]:
        """加载配置文件"""
        try:
            with open(self.config_file, 'r', encoding='utf-8') as f:
                return json.load(f)
        except FileNotFoundError:
            logger.error(f"配置文件不存在: {self.config_file}")
            return self.get_default_config()
        except Exception as e:
            logger.error(f"配置文件加载失败: {e}")
            return self.get_default_config()

    def get_default_config(self) -> Dict[str, Any]:
        """获取默认配置"""
        return {
            "app_info": {
                "name": "LimeReader",
                "chinese_name": "影谷长图处理大师",
                "bundle_id": "com.limereader.desktop",
                "version": "1.0.0",
                "copyright": "Copyright © 2025 LimeReader. All rights reserved."
            },
            "build_config": {
                "build_dir": "build",
                "cmake_config": "Release"
            },
            "macos_config": {
                "min_system_version": "10.15",
                "category": "public.app-category.graphics-design",
                "high_res_capable": True,
                "supports_sudden_termination": True,
                "supports_natural_scrolling": True
            }
        }

    def find_qt_installation(self) -> Optional[str]:
        """查找Qt安装路径"""
        qt_paths = [
            "/opt/homebrew",
            "/usr/local",
            "/Applications/Qt",
            "~/Qt",
            "/opt/qt"
        ]

        qt_versions = ["6.9.2", "6.8.1", "6.8.0", "6.7.0", "6.6.0"]

        for base_path in qt_paths:
            base_path = Path(base_path).expanduser()
            if not base_path.exists():
                continue

            for version in qt_versions:
                qt_path = base_path / f"qt-{version}"
                if qt_path.exists():
                    qt_bin = qt_path / "bin"
                    if (qt_bin / "qmake").exists() or (qt_bin / "qmake.app").exists():
                        logger.info(f"找到Qt安装: {qt_path}")
                        return str(qt_path)

        # 检查系统PATH中的qmake
        try:
            result = subprocess.run(['which', 'qmake'], capture_output=True, text=True)
            if result.returncode == 0:
                qmake_path = Path(result.stdout.strip())
                qt_bin = qmake_path.parent
                qt_path = qt_bin.parent
                logger.info(f"在PATH中找到Qt: {qt_path}")
                return str(qt_path)
        except:
            pass

        logger.error("未找到Qt安装")
        return None

    def find_deployment_tool(self) -> Optional[str]:
        """查找macdeployqt"""
        if self.qt_path:
            # 在Qt安装目录中查找
            bin_path = Path(self.qt_path) / "bin"
            if (bin_path / "macdeployqt").exists():
                tool_path = bin_path / "macdeployqt"
                logger.info(f"找到macdeployqt: {tool_path}")
                return str(tool_path)

        # 检查系统PATH
        try:
            result = subprocess.run(['which', 'macdeployqt'], capture_output=True, text=True)
            if result.returncode == 0:
                tool_path = result.stdout.strip()
                logger.info(f"在PATH中找到macdeployqt: {tool_path}")
                return tool_path
        except:
            pass

        logger.error("未找到macdeployqt")
        return None

    def create_app_bundle(self, executable_path: Path, version: str, output_dir: Path = None) -> Dict[str, Any]:
        """
        创建.app包

        Args:
            executable_path: 可执行文件路径
            version: 版本号
            output_dir: 输出目录

        Returns:
            创建结果字典
        """
        if not executable_path.exists():
            return {
                'success': False,
                'error': f'可执行文件不存在: {executable_path}'
            }

        logger.info(f"开始创建.app包: {executable_path.name}")

        # 设置输出目录
        if output_dir is None:
            output_dir = Path('dist')
        output_dir.mkdir(exist_ok=True)

        # 查找Qt安装
        self.qt_path = self.find_qt_installation()
        if not self.qt_path:
            logger.warning("未找到Qt安装，将创建基本的.app包")

        # 查找部署工具
        self.deployment_tool = self.find_deployment_tool()

        try:
            app_info = self.config['app_info']
            app_name = app_info['name']
            bundle_id = app_info['bundle_id']

            # 1. 创建.app包结构
            app_bundle_path = output_dir / f"{app_name}.app"
            contents_dir = app_bundle_path / "Contents"
            macos_dir = contents_dir / "MacOS"
            resources_dir = contents_dir / "Resources"
            frameworks_dir = contents_dir / "Frameworks"
            plugins_dir = contents_dir / "PlugIns"

            # 创建目录结构
            for dir_path in [contents_dir, macos_dir, resources_dir, frameworks_dir, plugins_dir]:
                dir_path.mkdir(parents=True, exist_ok=True)

            logger.info(f"创建.app包结构: {app_bundle_path}")

            # 2. 复制可执行文件
            executable_name = app_name
            executable_dest = macos_dir / executable_name
            shutil.copy2(executable_path, executable_dest)
            executable_dest.chmod(0o755)  # 设置执行权限
            logger.info(f"复制可执行文件: {executable_name}")

            # 3. 创建Info.plist
            info_plist_content = self.create_info_plist(version)
            info_plist_path = contents_dir / "Info.plist"
            with open(info_plist_path, 'w', encoding='utf-8') as f:
                f.write(info_plist_content)
            logger.info("创建Info.plist")

            # 4. 复制资源文件
            self.copy_resources(resources_dir)

            # 5. 如果找到了macdeployqt，使用它来部署Qt依赖
            if self.deployment_tool:
                self.deploy_qt_dependencies(app_bundle_path)

            # 6. 创建PkgInfo
            pkginfo_path = contents_dir / "PkgInfo"
            with open(pkginfo_path, 'w') as f:
                f.write("APPL????")
            logger.info("创建PkgInfo")

            # 7. 验证.app包
            if self.verify_app_bundle(app_bundle_path):
                logger.info(f".app包创建成功: {app_bundle_path}")
                return {
                    'success': True,
                    'app_path': str(app_bundle_path),
                    'app_name': f"{app_name}.app",
                    'bundle_id': bundle_id,
                    'version': version,
                    'message': '.app包创建成功'
                }
            else:
                return {
                    'success': False,
                    'error': '.app包验证失败'
                }

        except Exception as e:
            error_msg = f"创建.app包失败: {e}"
            logger.error(error_msg)
            return {
                'success': False,
                'error': error_msg
            }

    def create_info_plist(self, version: str) -> str:
        """创建Info.plist内容"""
        app_info = self.config['app_info']
        macos_config = self.config.get('macos_config', {})

        info_plist = f'''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleExecutable</key>
    <string>{app_info['name']}</string>
    <key>CFBundleIdentifier</key>
    <string>{app_info['bundle_id']}</string>
    <key>CFBundleName</key>
    <string>{app_info['name']}</string>
    <key>CFBundleDisplayName</key>
    <string>{app_info['chinese_name']}</string>
    <key>CFBundleVersion</key>
    <string>{version}</string>
    <key>CFBundleShortVersionString</key>
    <string>{version}</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleSignature</key>
    <string>????</string>
    <key>CFBundleSupportedPlatforms</key>
    <array>
        <string>MacOSX</string>
    </array>
    <key>LSMinimumSystemVersion</key>
    <string>{macos_config.get('min_system_version', '10.15')}</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>NSHighResolutionCapable</key>
    <{'true' if macos_config.get('high_res_capable', True) else 'false'}/>
    <key>NSSupportsAutomaticGraphicsSwitching</key>
    <true/>
    <key>NSSupportsAutomaticTermination</key>
    <{'true' if macos_config.get('supports_natural_scrolling', True) else 'false'}/>
    <key>NSRequiresAquaSystemAppearance</key>
    <false/>
    <key>CFBundleDocumentTypes</key>
    <array>
        <dict>
            <key>CFBundleTypeExtensions</key>
            <array>
                <string>jpg</string>
                <string>jpeg</string>
                <string>png</string>
                <string>gif</string>
                <string>bmp</string>
                <string>tiff</string>
                <string>webp</string>
                <string>pdf</string>
            </array>
            <key>CFBundleTypeName</key>
            <string>Image File</string>
            <key>CFBundleTypeRole</key>
            <string>Editor</string>
            <key>LSHandlerRank</key>
            <string>Owner</string>
        </dict>
    </array>
    <key>NSHumanReadableCopyright</key>
    <string>{app_info.get('copyright', 'Copyright © 2025 LimeReader. All rights reserved.')}</string>
    <key>CFBundleIconFile</key>
    <string>AppIcon</string>
</dict>
</plist>'''
        return info_plist

    def copy_resources(self, resources_dir: Path):
        """复制资源文件"""
        # 尝试从项目根目录复制资源
        project_root = Path(__file__).parent.parent
        possible_resource_dirs = [
            project_root / "resources",
            project_root / "icons",
            project_root / "images"
        ]

        for resource_dir in possible_resource_dirs:
            if resource_dir.exists():
                logger.info(f"复制资源文件: {resource_dir}")
                shutil.copytree(resource_dir, resources_dir / resource_dir.name, dirs_exist_ok=True)

        # 创建默认图标（如果不存在）
        icon_path = resources_dir / "AppIcon.icns"
        if not icon_path.exists():
            self.create_default_icon(icon_path)

    def create_default_icon(self, icon_path: Path):
        """创建默认应用图标"""
        # 这是一个简化的图标创建，实际应用中应该提供专业的.icns文件
        logger.info("创建默认应用图标")
        # 创建一个简单的文本文件作为占位符
        with open(icon_path, 'w') as f:
            f.write("# Default app icon placeholder\n# Replace with real .icns file")

    def deploy_qt_dependencies(self, app_bundle_path: Path):
        """使用macdeployqt部署Qt依赖"""
        if not self.deployment_tool:
            logger.warning("未找到macdeployqt，跳过Qt依赖部署")
            return

        logger.info("使用macdeployqt部署Qt依赖")

        try:
            cmd = [self.deployment_tool, str(app_bundle_path), "-verbose=2"]
            if self.qt_path:
                # 如果指定了Qt路径，添加到环境变量
                env = os.environ.copy()
                env['QTDIR'] = self.qt_path
                result = subprocess.run(cmd, capture_output=True, text=True, env=env)
            else:
                result = subprocess.run(cmd, capture_output=True, text=True)

            if result.returncode == 0:
                logger.info("Qt依赖部署成功")
                if result.stdout:
                    logger.info(f"macdeployqt输出: {result.stdout}")
            else:
                logger.error(f"Qt依赖部署失败: {result.stderr}")

        except Exception as e:
            logger.error(f"部署Qt依赖异常: {e}")

    def verify_app_bundle(self, app_bundle_path: Path) -> bool:
        """验证.app包"""
        try:
            # 检查基本结构
            required_paths = [
                app_bundle_path / "Contents",
                app_bundle_path / "Contents" / "Info.plist",
                app_bundle_path / "Contents" / "MacOS",
                app_bundle_path / "Contents" / "Resources"
            ]

            for path in required_paths:
                if not path.exists():
                    logger.error(f"缺少必要文件或目录: {path}")
                    return False

            # 检查可执行文件
            app_info = self.config['app_info']
            executable_path = app_bundle_path / "Contents" / "MacOS" / app_info['name']
            if not executable_path.exists():
                logger.error(f"可执行文件不存在: {executable_path}")
                return False

            # 检查可执行权限
            if not os.access(executable_path, os.X_OK):
                logger.error(f"可执行文件没有执行权限: {executable_path}")
                return False

            logger.info(".app包验证通过")
            return True

        except Exception as e:
            logger.error(f"验证.app包失败: {e}")
            return False

    def create_mock_app(self, version: str, output_dir: Path = None) -> Dict[str, Any]:
        """
        创建模拟.app包（用于测试）

        Args:
            version: 版本号
            output_dir: 输出目录

        Returns:
            创建结果字典
        """
        logger.info("创建模拟.app包用于测试")

        if output_dir is None:
            output_dir = Path('dist')
        output_dir.mkdir(exist_ok=True)

        app_info = self.config['app_info']
        app_name = app_info['name']
        app_bundle_path = output_dir / f"{app_name}.app"

        try:
            # 创建.app结构
            contents_dir = app_bundle_path / "Contents"
            macos_dir = contents_dir / "MacOS"
            resources_dir = contents_dir / "Resources"

            for dir_path in [contents_dir, macos_dir, resources_dir]:
                dir_path.mkdir(parents=True, exist_ok=True)

            # 创建Info.plist
            info_plist_content = self.create_info_plist(version)
            info_plist_path = contents_dir / "Info.plist"
            with open(info_plist_path, 'w', encoding='utf-8') as f:
                f.write(info_plist_content)

            # 创建模拟可执行文件
            executable_path = macos_dir / app_name
            with open(executable_path, 'w') as f:
                f.write(f'''#!/bin/bash
echo "{app_info['chinese_name']} v{version}"
echo "这是一个模拟的可执行文件，用于测试.app包结构"
echo "请替换为真实的可执行文件"
''')
            executable_path.chmod(0o755)

            # 创建PkgInfo
            pkginfo_path = contents_dir / "PkgInfo"
            with open(pkginfo_path, 'w') as f:
                f.write("APPL????")

            # 创建资源文件
            icon_path = resources_dir / "AppIcon.icns"
            with open(icon_path, 'w') as f:
                f.write("# Mock icon file")

            logger.info(f"模拟.app包创建成功: {app_bundle_path}")

            return {
                'success': True,
                'app_path': str(app_bundle_path),
                'app_name': f"{app_name}.app",
                'bundle_id': app_info['bundle_id'],
                'version': version,
                'message': '模拟.app包创建成功'
            }

        except Exception as e:
            error_msg = f"创建模拟.app包失败: {e}"
            logger.error(error_msg)
            return {
                'success': False,
                'error': error_msg
            }

def main():
    """主函数"""
    import argparse

    parser = argparse.ArgumentParser(description='LimeReader macOS .app创建工具')
    parser.add_argument('--config', default='release_config.json', help='配置文件路径')
    parser.add_argument('--executable', help='可执行文件路径')
    parser.add_argument('--version', required=True, help='版本号')
    parser.add_argument('--output-dir', help='输出目录')
    parser.add_argument('--mock', action='store_true', help='创建模拟.app包（用于测试）')

    args = parser.parse_args()

    try:
        creator = MacOSAppCreator(args.config)

        if args.mock:
            # 创建模拟.app包
            result = creator.create_mock_app(args.version, Path(args.output_dir) if args.output_dir else None)
        else:
            # 创建真实.app包
            if not args.executable:
                print("❌ 创建真实.app包需要提供 --executable 参数")
                return 1

            executable_path = Path(args.executable)
            result = creator.create_app_bundle(executable_path, args.version, Path(args.output_dir) if args.output_dir else None)

        if result['success']:
            print(f"✅ .app包创建成功!")
            print(f"应用路径: {result.get('app_path', 'N/A')}")
            print(f"Bundle ID: {result.get('bundle_id', 'N/A')}")
            print(f"版本: {result.get('version', 'N/A')}")
            return 0
        else:
            print(f"❌ .app包创建失败: {result.get('error')}")
            return 1

    except Exception as e:
        print(f"❌ 执行失败: {e}")
        return 1

if __name__ == '__main__':
    sys.exit(main())